/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CalculableUtopiaEntity;
import interfaces.CommandHandler;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import main.Bot;
import main.CommonMethods;
import main.Main;
import main.UserCentral;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import tools.InputType;
import tools.UtopiaTime;
import botobjects.Building;
import botobjects.Science;
import database.DBConnectionFactory;

/**
 * This class takes care of functions related to Utopia
 * 
 * 
 */

public class UtopiaHandler extends CommandHandler {
   private static UtopiaHandler INSTANCE = null;

   public static final UtopiaHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new UtopiaHandler();
	  }
	  return INSTANCE;
   }

   /**
    * Returns the current tick length
    * 
    * @return
    */
   public static int getTickLength() {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  int out = 60;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT ticklength FROM settings");
		 rs = ps.executeQuery();
		 if (rs.next()) {
			out = rs.getInt("ticklength");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Loads the current known buildings into a map, for fast access
    * 
    * @return
    */
   public static Map<String, Building> loadBuildings() {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  Map<String, Building> out = new HashMap<String, Building>();
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM building");
		 rs = ps.executeQuery();

		 while (rs.next()) {
			out.put(rs.getString("abbreviation"), new Building(rs.getString("name"), rs.getString("formula"),
				  rs.getString("cap"), rs.getString("syntax"), rs.getString("result")));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Loads the known science categories into a map, for fast access
    * 
    * @return
    */
   public static Map<String, Science> loadSciences() {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  Map<String, Science> out = new HashMap<String, Science>();
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM science");
		 rs = ps.executeQuery();

		 while (rs.next()) {
			out.put(rs.getString("abbreviation"),
				  new Science(rs.getString("name"), rs.getString("formula"), rs.getString("syntax"), rs.getString("result")));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   public static final UtopiaHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   private final Map<String, Building> buildings;

   private final Map<String, Science> sciences;

   private UtopiaHandler() {
	  buildings = loadBuildings();
	  sciences = loadSciences();
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput output) {
   }

   public void handleBuildings(final IRCCommand command, final IRCOutput out) {
	  String output = "Short versions of the buildings: ";
	  for (final Entry<String, Building> e : buildings.entrySet()) {
		 output += e.getKey() + ",";
	  }
	  out.addOutput(output.substring(0, output.length() - 1));
   }

   public void handleEffects(final IRCCommand command, final IRCOutput out) {
	  CalculableUtopiaEntity calculable;
	  if (command.get(1).matches(CommonMethods.getBuildingsRegex())) {
		 calculable = buildings.get(command.get(1));
	  } else if (command.get(1).matches(CommonMethods.getScienceRegex())) {
		 calculable = sciences.get(command.get(1));
	  } else
		 return; // no such item exists
	  for (int i = 2; i < command.size(); ++i) { // check that the commands contains only numbers, and removes eventual %
		 // symbols
		 command.set(i, command.get(i).replace("%", ""));
		 if (!command.isDouble(i) && !command.isInt(i))
			return;
	  }
	  String[] formula = calculable.getFormula(command);
	  if (formula == null)
		 return;

	  CalculationHandler calc = CalculationHandler.getInstance();
	  ArrayList<String> out1 = new ArrayList<String>();
	  for (final String s : formula) {
		 out1.add(calc.calc(s));
	  }

	  for (int i = 0; i < out1.size(); ++i) // put together the output strings
	  {
		 String temp = out1.get(i);
		 temp = Colors.removeColors(temp);
		 temp = temp.replace(",", "");
		 if (!temp.matches(".* = [0-9.]{1,}"))
			return;
		 temp = temp.substring(temp.indexOf('=') + 1).trim(); // only want the result
		 Double result = Double.parseDouble(temp);
		 final Double cap = Double.parseDouble(calculable.getCap()[i]);
		 if (cap > 0) {
			result = Math.min(result, cap); // if there's a cap, apply it. 0 = no cap
		 }

		 if (calculable.getResult() == null)
			return;
		 final NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
		 nf.setMaximumFractionDigits(2);
		 nf.setMinimumFractionDigits(0);
		 nf.setGroupingUsed(true);
		 out.addOutput(Colors.RED + calculable.getName() + Colors.OLIVE + " effect: " + Colors.BLUE + nf.format(result)
			   + calculable.getResult()[i]);
	  }
   }

   public void handleSetagestart(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 4 && command.validate(1, 4, InputType.UTODATE)
			&& UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 if (UtopiaTime.setStartDate(command.getFullMessage(), Bot.getInstance())) {
			out.addOutput("Age start set");
		 }
	  }
   }

   public void handleSetticklength(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.isInt(1)) {
		 Main.getBotSettings().setTickLength(Integer.parseInt(command.get(1)));
		 out.addOutput(setTickLength(Integer.parseInt(command.get(1))));
	  }
   }

   /**
    * Saves the tick length
    */
   private List<String> setTickLength(final int tickLength) {
	  PreparedStatement ps = null;
	  List<String> out = new LinkedList<String>();
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("UPDATE settings SET ticklength = ?");
		 ps.setInt(1, tickLength);
		 ps.executeUpdate();
		 out.add("Ticklength set to " + tickLength);
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }
}
